Android 开发小技巧

前言

做了许久的开发,常常有些小细节会打动到我,希望这篇文章可以记录这些分享给大家

布局文件

tools命名空间在预览布局时的应用

在布局编写时常常因查看布局效果会添加一些正式环境不会初始化的属性,比如说 text,visibility… 但是为了初始化效果,这些属性一般会在完成后删除,有时候在维护时为了方便预览再写上,这时可以使用 tools 的命名空间。如

1
2
3
4
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="这段话只在预览时展示,正式环境不会显示" />

在运行的状态下解析布局文件时就会忽略 tools 属性

android.widget.Space 占位控件

一个占位轻量级控件,绘制过程中会跳过 onDraw() 步骤,使用与分隔不同的控件展示一片空白

Merge 合并标签

此标签可以节省一层布局深度。常常运用在 include 标签之中,一般情况下我们将 include 的根标签用 merge 代替,而不用再新建一个 ViewGroup,同时在自定义继承自系统 ViewGroups 时在写布局文件时也需要用到,可以避免一层布局深度;可以通过载入一个带有标签的布局文件来自动定义它的子部件

1
2
3
4
<merge
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:parentTag="android.widget.RelativeLayout"

通过 tools:parentTag 可以预览富布局效果

android:duplicateParentState (View属性)

当改属性设置为 true 时,子 View 可以复制父 View 的状态(selected, focused 等状态)。比如如果一个 ViewGroup 是可点击的,那么可以用这个属性在它被点击的时候让它的子 View 都改变状态

android:clipChildren (ViewGroup属性)

定义了子 View 是否仅限于在 Padding 里绘制。此属性默认设置为 true 即子 View 绘制无法超过 ViewGroup 的 padding 区域,当设置为 false 时,那么 ViewGroup 的子 View 在绘制的时候会超出它的范围,往往会在做动画的时候需要用到

android:clipToPadding (ViewGroup属性)

定义了 ViewGroup 是否会裁剪子 View 调整边缘效果保证 padding 效果,当 ViewGroup 设置了 padding 后该属性默认为 true。
运用场景:有时 ScrollView, RecyclerView 等会设置一个离顶部 toolbar 及屏幕底部一定距离来留白,默认情况下,滚动是在 padding 范围内的,会出现内容的上下滚动边界离真正的边界有一定的距离。此时只需要将该属性设置为 false,滚动时子控件就能绘制到父控件的 padding 区域

android:animateLayoutChanges (ViewGroup属性)

ViewGroup 中添加或移除 View 时(对 Visibility 设置也起作用)设置动画的办法,支持通过 setLayoutTransition() 自定义动画

android:fillViewport (ScrollView属性)

该属性决定了滚动视图是否应该伸展它的内容填补视窗。就是要让 ScrollView 内部元素的 match_parent 起作用将该属性设置为 true

app:layoutManager (RecyclerView属性)

RecyclerView 可以在 xml 布局文件中设置 LayoutManager,可以不用在代码中创建新的 LayoutManager 实例,RecyclerView 在 style 中已定义

1
2
3
4
5
6
7
<declare-styleable name="RecyclerView">
<attr name="layoutManager" format="string"/>
<attr name="android:orientation"/>
<attr name="spanCount" format="integer"/>
<attr name="reverseLayout" format="boolean"/>
<attr name="stackFromEnd" format="boolean"/>
</declare-styleable>

TextView

TextView.setError()

可以设置文字或者文字加上图片,展示一个 popupWindow 效果非常好

TextView去除上下留白

TextView 默认上下是有一定的 padding 的,有时候我们可能不需要上下这部分留白,在 xml 布局文件中加上 includeFontPadding="false" 即可

TextView设置图片

xml布局文件中设置:

1
2
3
<TextView
......
android:drawableLeft = "@drawable/icon"/>

或者可以在 java 代码文件中设置:

1
2
3
4
5
6
7
8
9
10
Drawable img = ContextCompat.getDrawable(this, R.drawable.icon);
img.setBounds(0,0,100,100);
textView.setCompoundDrawables(img,null,null,null);

/* ********* or ********* */
Drawable img = ContextCompat.getDrawable(this, R.drawable.icon);
textView.setCompoundDrawablesWithIntrinsicBounds(img,null,null,null);

/* ********* or ********* */
textView.setCompoundDrawablesWithIntrinsicBounds(resId,0,0,0);

TextView在文中设置特殊格式

setText(CharSequence text) 方法可以传入 SpannableString 类展示特殊格式,该类通过 setSpan(Object what, int start, int end, int flags) 方法可以设置各种不同的效果,比如说颜色,背景,点击事件等等,建议通过 SpannableStringBuilder 类得到实例或者 fromHtml(String source) 方法得到。

EditView

InputType

可以定义软键盘弹出的类型,在xml布局文件中的EditText标签下直接定义,比如说定义带小数的数字输入

1
2
3
<EditText 
......
android:inputType="number" />

也可以在 java 代码中设置

1
editText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);

可以在 xml 文件中设置 android:inputType="textUri" 或者代码中调用 setInputType(InputType.TYPE_TEXT_VARIATION_URI)实现英文键盘弹出

setKeyListener(KeyListener)

通过 DigitsKeyListener 类,使用 getInstance(String accepted) 方法即可指定EditText可输入字符集
同时也在代码中可以通过 setFilters(InputFilter[] filters) 设置过滤器来实现 EditText 的定向输入。比如说两位小数的输入的自定义 InputFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DecFilter implements InputFilter {
@Override
public CharSequence filter(CharSequence source,
int start, int end, Spanned dest, int dstart, int dend) {
//不能在倒数第三位以前输入点
if (".".equals(source.toString()) && dstart + 2 < dest.length()) return "";
//控制小数位数为两位
String[] splitArray = dest.toString().split("\\.");
if (splitArray.length > 1) {
//如果已经含有两位小数,判断插入的地方是否为小数位,是则过滤
if (splitArray[1].length() == 2 && dstart + 2 >= dest.length()) {
return "";
} else {
return source;
}
}
return source;
}
}

与InputType配合可以实现两位小数输入限制

软键盘适配

为了解决弹出软键盘遮挡 EditText 的冲突,一般会在 manifests 文件对应 activity 标签下添加

1
2
3
<activity
......
android:windowSoftInputMode="adjustPan|stateAlwaysHidden"/>

但在 EditView 为多行输入且文本高度超过控件高度时,当隐藏键盘后点击超过控件高度的文本位置时,软键盘的弹出会遮盖 EditText,此时需要将 adjustPan 替换成 adjustResize

点击外部隐藏软键盘

可以在activity的基类的 dispatchTouchEvent(MotionEvent ev) 中添加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
View v = getCurrentFocus();
if (v != null && (v instanceof EditText)) {
int[] point = {0, 0};
v.getLocationOnScreen(point);
int left = point[0], top = point[1], bottom = top + v.getHeight(), right = left + v.getWidth();
if (!(ev.getRawX() > left && ev.getRawX() < right && ev.getRawY() > top && ev.getRawY() < bottom)) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0);
v.clearFocus();
}
}
}
return super.dispatchTouchEvent(ev);}

软键盘的监听

Android 系统并没有提供软键盘监听接口,但某些场景却需要对键盘进行监听,此时可以采用特殊的办法,那就是采用 Activity 的

1
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)

当 Activity 的键盘属性是 adjustResize 时,键盘弹出会挤压 activity 的布局,此时可以通过 (bottom - oldBottom > keyHeight) 方法来认为键盘是否弹出,keyHeight 一般认为是屏幕高度的 1/3

RecyclerView

自动测量

在 Support Library 23.2 以后支持自动测量,现在可以非常方便将 Recycler 嵌套在 ScrollView 及其他 ViewGroup 中,可以设置 warp_content 用来自动测量

当嵌套在 ScrollView 中时,因为滑动方向一致导致 RecyclerView 部分会由粘滞感,需要调用方法 setNestedScrollingEnabled(false) 但在高 sdk 上有bug,此时的 RecyclerView 未加载的 item 将不会显示,解决办法是将 ScrollView 替换成 NestedScrollView

自定义View

列举一些对自定义View有帮助的类或方法

  • canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
    该方法可以用来清除画布上的内容

  • canvas.clipRect()、clipPath()
    该方法可以用来剪切画布区域

  • onDetachedFromWindow
    复写该方法中清理与 View 相关的资源

  • getParent().requestDisallowInterceptTouchEvent(true)
    方法可以剥夺父 View 对 touch 事件的处理权,可用于滑动冲突

  • ArgbEvaluator.evaluate
    用于根据一个起始颜色值和一个结束颜色值以及一个偏移量生成一个新的颜色,可用于自定义 View 的动画

  • ViewDragHelper
    用于自定义 ViewGroup 处理各种事件

  • GestureDetector
    用来监听和相应对应的手势事件,比如点击,长按,慢滑动,快滑动

  • ViewConfiguration.getScaledTouchSlop()
    触发移动事件的最小距离,这是系统提供的用于自定义 View 处理 touch 事件的时候判断用户是否滑动的最小距离

  • StaticLayout
    该类一般用于在自定义 View 中渲染文字

  • Paint.setXfermode(porterDuffXfermode)
    在 ApiDemo 里面有专门的介绍,实现了穿透,叠加,覆盖等多种绘制效果,非常实用

  • performClick() 和 performLongClick()
    方法可以分别模拟View的点击和长按

  • VelocityTracker
    可用于 View 滑动事件速度跟踪

其他

Fragment 类中的 onHiddenChanged(boolean) 方法

该会在 FragmentTransaction 的 hide() 和 show() 调用时被 Fragment 调用,而 Fragment 的其他生命周期方法并不会在此时被调用,所以一般的刷新等操作不能在 onResume() 方法中实现

Fragment.setArguments(Bundle bundle)

在构建 Fragment 的时候不能加参数,我们可以通过将参数传入 Bundle 中,通过此方法传入到 Fragment 实例中(即使在 configuration 改变的时候仍然会导致销毁/重建)

FragmentManager.enableDebugLogging()

可以帮助观察 Fragment 状态

PageTransformer 接口

可以用来定义 ViewPager 页面切换的动画,通过 setPageTransformer(boolean, PageTransformer) 方法实现

Resources 类中的 getIdentifier(name,defType,defPackage)

方法可以通过资源名称获取其ID,可以在多种不同类型资源获取中运用

ViewStub

该类是一个初始化不做任何事情的 View,但是之后可以载入一个布局文件。在慢加载 View 中很适合做占位符。唯一的缺点就是不支持标签,所以如果你不太小心的话,可能会在视图结构中加入不需要的嵌套

View保存为Bitmap

除了ScrollView外可以使用下面方法:

1
2
3
4
5
6
7
public static Bitmap createBitmap(View v) {
v.setDrawingCacheEnabled(true);
v.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(v.getDrawingCache());
v.setDrawingCacheEnabled(false);
return bitmap;
}

而ScrollView则需要使用下面方法:

1
2
3
4
5
6
7
8
9
10
11
public static Bitmap createBitmap(ScrollView v) {
int width = 0, height = 0;
for (int i = 0; i < v.getChildCount(); i++) {
width += v.getChildAt(i).getWidth();
height += v.getChildAt(i).getHeight();
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
v.draw(canvas);
return bitmap;
}

android.text.TextUtils

该类集成了许多关于字符串的常用方法。比如说判断字符串是否为 null 或 “” 的 isEmpty(CharSequence) 方法;判断字符串是否相同的 equals(CharSequence a, CharSequence b) 方法(使用 a.equals(b) 方法需要对 a 做非空判断)等

android.util.Pair

该类可以存储一组数据,但并不是以key-value形式存在,而是两个参数单独存在

android.graphics.PointF

该类可以用来描述坐标点,比起单独的x,y更加方便简洁,还包括了一些简单的坐标计算函数

android.util.SparseArray

当 key 为 int 类型时,SparseArray 可以用来替换 hashMap,它可以避免操作数据池时的大量装箱拆箱过程以便节省内存,提高性能。还有一些继承类 SparseBooleanArray、SparseIntArray 和 SparseLongArray

android.database.DatabaseUtils

类是用于提供操作数据库的通用方法

java.math.BigDecimal

该类是用于高精度计算的类,可以直接传入字符串,返回类型和格式可以通过相关方法自定义,较复杂的计算推荐这个类。这个类有一个相关的 DecimalFormat 类,用于字串格式化包括指定位数、百分数、科学计数法等

##感谢
Android开发中,有哪些让你觉得相见恨晚的方法、类或接口
awesome-android-tips